<template>
  <div class="relative" @mouseenter="onMouseenter" @mouseleave="onMouseleave">
    <div ref="referenceTarget">
      <!--   具名插槽:触发弹层中的视图 -->
      <slot name="reference"></slot>
    </div>
    <!--  气泡展示-->
    <transition name="slide">
      <div ref="contentTarget" v-show="isVisable"
           class="absolute p-1 z-20 bg-white dark:bg-zinc-900 border dark:border-zinc-700 rounded-md"
           :style="contentStyle">
        <!--   匿名插槽:弹出层视图中展示的内容 -->
        <slot/>
      </div>
    </transition>
  </div>
</template>

<script>
//延迟关闭时间
const DELAY_TIME = 500
// 左上
const PROP_TOP_LEFT = 'top-left'
// 右上
const PROP_TOP_RIGHT = 'top-right'
// 左下
const PROP_BOTTOM_LEFT = 'bottom-left'
// 右下
const PROP_BOTTOM_RIGHT = 'bottom-right'
const placementEnum = [PROP_TOP_LEFT, PROP_TOP_RIGHT, PROP_BOTTOM_LEFT, PROP_BOTTOM_RIGHT]
</script>

<script setup>
/**
 * vue3 允许template中有多个根元素，但是某些情况会有问题
 */
/**
 * 1.指定可选位置的常量，并生成enum对象
 * 2.通过prop控制指定位置
 * 3.获取元素的DOM。创建读取元素尺寸的方法
 * 4.生成气泡的样式对象，用来控制每个位置对应的样式
 * 5.根据prop，计算样式对象
 */
import {nextTick, ref, watch} from "vue";

const props = defineProps({
  //控制气泡弹出位置
  placement: {
    type: String,
    default: PROP_BOTTOM_LEFT,
    validator(value) {
      const result = placementEnum.includes(value)
      if (!result) {
        throw new Error(`你的placement必须是${placementEnum.join('、')}中的一个`)
      }
      return result
    }
  }
})
const isVisable = ref(false)

let timeout
/**
 * 鼠标移入
 */
const onMouseenter = () => {
  isVisable.value = true
  if (timeout) {
    clearTimeout(timeout)
  }
}

/**
 * 鼠标移出
 */
const onMouseleave = () => {
  timeout = setTimeout(() => {
    isVisable.value = false
    timeout = null
  }, DELAY_TIME)
}
/**
 * 计算元素尺寸
 */
const referenceTarget = ref(null)
const contentTarget = ref(null)
/**
 * 获取元素的DOM。创建读取元素尺寸的方法
 * @param target
 * @returns {{width: number, height: number}|{}}
 */
const useElementSize = (target) => {
  if (!target) return {}
  return {
    width: target.offsetWidth,
    height: target.offsetHeight
  }
}
/**
 * 气泡样式
 */
const contentStyle = ref({
  top: 0,
  left: 0
})
/**
 * 计算：期望在气泡展示时再计算
 */
watch(isVisable, (val) => {
  if (!val) return
  //等待元素渲染
  //vue中数据改变之后 ，需要等待一段时间 DOM才会变化,所以使用nextTick函数的参数，来监听DOM变化之后的回调
  nextTick(() => {
    switch (props.placement) {
        //左上
      case PROP_TOP_LEFT:
        contentStyle.value.top = 0
        contentStyle.value.left = -useElementSize(contentTarget.value).width + 'px'
        break
        //右上
      case PROP_TOP_RIGHT:
        contentStyle.value.top = 0
        contentStyle.value.left = useElementSize(referenceTarget.value).width + 'px'
        break
        //左下
      case PROP_BOTTOM_LEFT:
        contentStyle.value.top = useElementSize(referenceTarget.value).height + 'px'
        contentStyle.value.left = -useElementSize(contentTarget.value).width + 'px'
        break
        //右下
      case PROP_BOTTOM_RIGHT:
        contentStyle.value.top = useElementSize(referenceTarget.value).height + 'px'
        contentStyle.value.left = useElementSize(referenceTarget.value).width + 'px'
        break
    }
    // useElementSize(contentTarget.value)
  })
})
</script>

<style lang="scss" scoped>
// slide 展示动画
.slide-enter-active {
  transition: opacity 0.3s, transform 0.3s;
}

.slide-leave-active {
  transition: opacity 0.3s, transform 0.3s;
}

.slide-enter-from,
.slide-leave-to {
  transform: translateY(20px);
  opacity: 0;
}
</style>